home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
C/C++ Users Group Library 1996 July
/
C-C++ Users Group Library July 1996.iso
/
listings
/
v_02_12
/
2n12040a
< prev
next >
Wrap
Text File
|
1991-07-14
|
8KB
|
308 lines
/*
* Listing 1: TOSPKR.C
* Play an audio file file out the PC speaker.
* Written for Turbo C 2.0, by Bob Bybee, 7/91
*
* usage: tospkr <filename> [<bits>]
*
* <filename> is the audio file to be played.
*
* <bits> is the number of speaker bits per sample.
* if not entered, it will be calculated.
*/
#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
#include <bios.h>
#include <alloc.h>
#include <dos.h>
#include <conio.h>
#ifndef __LARGE__
#error must use LARGE memory model!
#endif
/* audio sample rate, per second */
#define SAMPRATE 8000
/*
* 80x86 opcodes for the instruction sequences we
* will need, in order to drive the PC speaker.
*
* On entry, to set up the registers:
* BA 61 00 mov dx,61h
* B5 4A mov ch,4ah
* B1 48 mov cl,48h
*
* (Note: the 4a/48 values, at offset 4/6, should be
* changed to agree with the current value of port 61H.)
*
* To set the speaker bit high:
* 8A C5 mov al,ch
* EE out dx,al
*
* To set it low:
* 8A C1 mov al,cl
* EE out dx,al
*
* A "far return" to end the routine:
* CB retf
*/
unsigned char ops_begin[] =
{0xba, 0x61, 0x00, 0xb5, 0x4a, 0xb1, 0x48};
unsigned char ops_high[] = {0x8a, 0xc5, 0xee};
unsigned char ops_low[] = {0x8a, 0xc1, 0xee};
unsigned char ops_end[] = {0xcb};
/* input buffer size, and pointer to it */
#define INBUFSIZE 65000U
unsigned char *inbuf;
/* array of audio segment lengths in file */
#define MAX_SEGS 200
unsigned int seg_length[MAX_SEGS];
unsigned int num_segs = 0;
char got_lengths = 0;
/* PFV is a pointer to a function taking
* no args, and returning void */
typedef void (*PFV)( void );
PFV playfuncs[256];
unsigned int bits_per_sample;
/* prototypes */
void patch_opcodes( void );
int best_rate( void );
int test_time( int n_bits );
void generate_codes( void );
void main( int ac, char **av )
{
FILE *fp;
unsigned int nbytes, len, seg, n_to_read;
unsigned char *p;
--ac;
++av;
if (ac < 1)
{
printf("usage: tospkr <voicefile> [<bits>]\n");
exit(1);
}
/* open the input file */
if ((fp = fopen(*av, "rb")) == NULL)
{
printf("Can't open voice file: %s\n", *av);
exit(1);
}
/* allocate memory for the input buffer */
if ((inbuf = calloc(1, INBUFSIZE)) == NULL)
{
printf("Can't get memory for input buffer!\n");
exit(1);
}
/* get the current value of port 61H */
patch_opcodes();
if (ac >= 2 && (bits_per_sample = atoi(av[1])) > 0)
; /* got our sample rate on the command line */
else
bits_per_sample = best_rate();
/* Create the 256 executable functions */
generate_codes();
/* See if the file is prefixed with a list of audio
* segment lengths. If so, read it into the
* seg_length[] array. It contains unsigned ints,
* zero-terminated.
*/
fread(inbuf, 4, 1, fp);
if (memcmp(inbuf, "LIST", 4) == 0)
{
got_lengths = 1;
do {
fread(&len, 2, 1, fp);
seg_length[num_segs++] = len;
} while (len != 0);
}
else
; /* We could rewind here, but
* 4 bytes isn't worth it.
*/
/* Read in the file and play it thru the speaker.
* On each read, get the next segment's worth (if a
* list of segments exists), or as much as we can.
*/
for (seg = 0; ; ++seg)
{
n_to_read =
got_lengths ? seg_length[seg] : INBUFSIZE;
if (n_to_read == 0 ||
(nbytes = fread(inbuf, 1, n_to_read, fp)) <= 0)
break;
p = inbuf;
disable();
/* For each byte, call one of 256 functions which
* were created in the playfuncs[] array.
*/
while (nbytes-- > 0)
(*playfuncs[*p++])();
enable();
if (kbhit()) /* if a key was hit, */
{
getch(); /* eat the key, */
break; /* and quit. */
}
}
fclose(fp);
exit(0);
}
/*
* Read from port 61H. Use this value with bit 1
* set and cleared, in ops_begin[] opcodes.
*/
void patch_opcodes( void )
{
int val;
val = inportb(0x61);
val &= ~0x03; /* lose bits 0, 1 */
ops_begin[6] = val;
ops_begin[4] = val | 0x02; /* add bit 1 */
}
/*
* Calculate the number of bits we can play to the
* speaker, for each audio sample, in order to make
* the playback come out at SAMPRATE samples
* per second.
*/
int best_rate( void )
{
int bits1, bits2, ticks1, ticks2;
float fbits;
/* Start with 10 bits/sample. Double it until
* it takes at least one second (18 ticks) */
printf("I'm timing your CPU, please wait...\n");
bits1 = 10;
while ((ticks1 = test_time(bits1)) < 18)
bits1 *= 2;
/* Now time it at 5x this number of bits. */
bits2 = bits1 * 5;
ticks2 = test_time(bits2);
/* Interpolate to get the optimum number of bits
* per sample for this PC. */
fbits = (18.2 - ticks1) * (bits2 - bits1);
fbits = fbits / (ticks2 - ticks1) + bits1;
return ((int)fbits);
}
/*
* Build a set of instructions into inbuf, which will
* play n_bits out to the speaker. Return the number
* of BIOS ticks it takes to play this many bits out,
* SAMPRATE times.
*/
int test_time( int n_bits )
{
unsigned char *p;
int i;
long t1, t2;
PFV testcode;
p = inbuf;
memcpy(p, ops_begin, sizeof(ops_begin));
p += sizeof(ops_begin);
for (i = 0; i < n_bits; ++i)
{
memcpy(p, ops_high, sizeof(ops_high));
p += sizeof(ops_high);
}
memcpy(p, ops_end, sizeof(ops_end));
testcode = (PFV)inbuf;
t1 = biostime(0, 0L);
for (i = 0; i < SAMPRATE; ++i)
(*testcode)();
t2 = biostime(0, 0L) - t1;
printf("bits: %d ticks: %d\n", n_bits, (int)t2);
return (int)t2;
}
/*
* Generate opcode streams, in allocated memory, to push
* the proper bit patterns out to the PC speaker.
*/
#define DEBUG_WAVE 0 /* 1 to enable printouts */
void generate_codes( void )
{
unsigned char *p;
unsigned int value, sum, i;
printf("generating code for %d bits per sample...\n",
bits_per_sample);
for (value = 0; value < 256; ++value)
{
/* Get memory for the next instruction stream
* we're going to create. */
p = calloc(1, sizeof(ops_begin) + sizeof(ops_end) +
bits_per_sample * sizeof(ops_high));
if (p == NULL)
{
printf("out of memory at value %d\n", value);
exit(1);
}
playfuncs[value] = (PFV)p;
#if DEBUG_WAVE
printf("\n%3d: ", value);
#endif
memcpy(p, ops_begin, sizeof(ops_begin));
p += sizeof(ops_begin);
/* evenly distribute the ones over the bits. */
sum = 0;
for (i = 0; i < bits_per_sample; ++i)
{
sum += value;
if (sum >= 255)
{
sum -= 255;
memcpy(p, ops_high, sizeof(ops_high));
#if DEBUG_WAVE
putchar('1');
#endif
}
else
{
memcpy(p, ops_low, sizeof(ops_low));
#if DEBUG_WAVE
putchar('.');
#endif
}
p += sizeof(ops_low);
}
memcpy(p, ops_end, sizeof(ops_end));
}
}